/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.form;

import com.jb.faces.context.FacesContext;
import com.jb.faces.el.AttributeEvaluator;
import com.jb.framework.ModelBean;
import com.jb.framework.PersistentContext;
import com.jb.framework.util.PropUtil;
import com.jb.util.ClassUtil;
import com.jb.util.StringUtil;
import com.jb.web.bean.Button;
import com.jb.web.bean.CheckBox;
import com.jb.web.bean.DateTextField;
import com.jb.web.bean.Form;
import com.jb.web.bean.FormField;
import com.jb.web.bean.Label;
import com.jb.web.bean.RadioButton;
import com.jb.web.bean.TableCell;
import com.jb.web.bean.TableRow;
import com.jb.web.bean.TextField;
import com.jb.web.bean.UIComponentImpl;
import com.jb.web.bean.inspector.OptionPropertyEditor;
import com.jb.web.context.WebContext;

import java.beans.PropertyDescriptor;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Create a form based on a Java Bean.
 */
public class BeanForm extends Form {
    static final long serialVersionUID = 123123L;

    static {
        publish(BeanForm.class, GENERAL, "generateForm");
        publish(BeanForm.class, DATA, "factoryClass");
        publish(BeanForm.class, DATA, "sourceClass");
        publish(BeanForm.class, DATA, "objectInstance");
        publish(BeanForm.class, EVENT_HANDLER, "onCreateInstance");
        publish(BeanForm.class, EVENT_HANDLER, "onUpdateInstance");
        publish(BeanForm.class, DATA, "objectInstance");
        publish(BeanForm.class, GENERAL, "beanAction", 
                new OptionPropertyEditor("static:,None;create,Create;update,Update;"));
    }

    private transient Class mClass;
    private Log log = LogFactory.getLog(getClass().getName());
    private Object modelBean;
    private String beanAction; //None, Create, Update, or Delete
    private String mFactoryClass;
    private String mSourceClass;
    private String objectInstance;

    //listner when a new instance is created
    private String onCreateInstance;

    //listner when the instance is updated
    private String onUpdateInstance;
    private boolean generateForm;
    private boolean _generateForm;


    public void setBeanAction(String beanAction) {
        this.beanAction = beanAction;
    }


    public String getBeanAction() {
        return beanAction;
    }

    //
    //If specified, this class will be used to create new instance
    //The class must have a static method createInstance(String className, Map)
    //

    public void setFactoryClass(String pFactoryClass) {
        this.mFactoryClass = pFactoryClass;
    }

    public String getFactoryClass() {
        return mFactoryClass;
    }


    public List getMetaData() {
        return com.jb.framework.util.PropUtil.getProperties(mClass);
    }

    public Object getModelBean() {
        return modelBean;
    }

    public void setObjectInstance(String objectInstance) {
        this.objectInstance = objectInstance;
    }


    public String getObjectInstance() {
        return objectInstance;
    }

    public void setOnCreateInstance(String onCreateInstance) {
        this.onCreateInstance = onCreateInstance;
    }


    public String getOnCreateInstance() {
        return onCreateInstance;
    }

    public void setOnUpdateInstance(String onUpdateInstance) {
        this.onUpdateInstance = onUpdateInstance;
    }

    public String getOnUpdateInstance() {
        return onUpdateInstance;
    }


    public void setSourceClass(String pSourceClass) {
        if ((pSourceClass != null) && (pSourceClass.length() != 0) && 
            !pSourceClass.equals(mSourceClass)) {
            mSourceClass = pSourceClass;
            mClass = getSourceClazz();
        }
    }

    public String getSourceClass() {
        return mSourceClass;
    }

    public static void assignFormToBean(Object bean, Form form) {
        HashMap mMap = new HashMap();
        Iterator iter = form.getFields().iterator();

        while (iter.hasNext()) {
            FormField ff = (FormField)iter.next();

            if ((ff != null) && ff instanceof RadioButton) {
                mMap.put(((RadioButton)ff).getGroupName(), 
                         ff.getDecodedValue());
            } else {
                mMap.put(ff.getFieldName(), ff.getDecodedValue());
            }
        }

        PropUtil.assignPropertiesFromMap(bean, mMap);
    }

    public Object createBeanInstance(WebContext ctx, Map properties) {
        createBeanInstance(ctx);
        if (modelBean != null) {
            PropUtil.assignPropertiesFromMap(modelBean, properties);
        }
        return modelBean;
    }

    public void processUpdates(FacesContext facesCtx) {
        WebContext ctx = (WebContext)facesCtx;

        //if bean action is null, we do nothing to the bean
        if (isMyEvent(ctx) && 
            ((beanAction != null) && (beanAction.length() != 0))) {
            createBeanInstance(ctx);

            if (modelBean instanceof ModelBean) {
                if (beanAction.equals("create")) {
                    try {
                        //this will fail if unique name constraints
                        ((ModelBean)modelBean).create(ctx);
                        
                        modelBean = null;
                    } catch (Exception e) {
                        if (log.isErrorEnabled()) {
                            log.error(this, e);
                        }
                        //ctx.addErrorMessage(this, e.getMessage());
                    }finally {
                        resetForm();
                    }
                } else if (beanAction.equals("update")) {
                    try {
                        ((ModelBean)modelBean).update(ctx);
                    } catch (Exception e) {
                        if (log.isErrorEnabled()) {
                            log.error(this, e);
                        }
                        ctx.addErrorMessage(this, "Failed to update bean " + e.getMessage());
                    }
                }
            }

            if (modelBean instanceof UIComponentImpl) {
                ((UIComponentImpl)modelBean).valueChanged(ctx);
            }
        }
        //so that event handler could see changes
        super.processUpdates(ctx);
    }

    public void updateView(WebContext ctx) {

        super.updateView(ctx);

        if (!ctx.isRenderResponse) {
            Object bean = getBeanInstance(ctx);
            populate(ctx, bean);
        }
    }

    public boolean valueChanged(PersistentContext ctx) {
        if (_generateForm) {
            this.generateForm((WebContext)ctx);
        }
        return super.valueChanged(ctx);
    }

    private Object getBeanInstance(WebContext ctx) {
        try {
            if ((objectInstance == null) || (objectInstance.length() == 0)) {
                modelBean = null;
            } else {
                modelBean = AttributeEvaluator.evaluate(ctx, this, objectInstance);
            }

            return modelBean;
        } catch (Exception e) {
            ctx.addErrorMessage(this, "Failed to create bean instance " + e);
        }

        return null;
    }

    /**
     * Setting Bean instance for this form. If objectInstance is assigned, the instance will be
     * retrieved through the expression, otherwise, a new instance will be created using class name
     * specified by field sourceClass. After bean created or retreived, the form value will be
     * assigned to the instance. LifeCycle this method is called when update model and update view
     *
     * @param ctx
     *
     * @return
     */
    private void createBeanInstance(WebContext ctx) {
        try {
            if ((objectInstance == null) || (objectInstance.length() == 0)) {
                Class clazz = getSourceClazz();
                if(!clazz.isInterface()) {
                    modelBean = clazz.newInstance();
                } else {
                    //factory
                }
                if (modelBean != null) {
                    // Map map = ctx.getRequest().getParameterMap();
                    assignFormToBean(modelBean, this);
                }
                if (onCreateInstance != null) {
                    handleEvent(ctx, onCreateInstance);
                }
            } else {
                modelBean = AttributeEvaluator.evaluate(ctx, this, objectInstance);
                if (modelBean != null) {
                    // Map map = ctx.getRequest().getParameterMap();
                    assignFormToBean(modelBean, this);
                }
            }
        } catch (Exception e) {
            ctx.addErrorMessage(this, "Failed to create bean instance " + e);
        }
    }

    public void setGenerateForm(boolean generateForm) {
        _generateForm = generateForm;
        this.generateForm = false;
    }

    public boolean isGenerateForm() {
        return generateForm;
    }

    private Class getSourceClazz() {
        return ClassUtil.getClass(getSourceClass());
    }

    private void generateForm(WebContext ctx) {
        Class clazz = getSourceClazz();
        if (clazz == null) {
            ctx.addErrorMessage(this, "Class not found " + this.mSourceClass);
            return;
        }

        _generateForm = false;
        clearChildren();
        List properties = PropUtil.getProperties(clazz);
        Iterator iter = properties.iterator();
        setColumns(1);
        setRows(properties.size() + 1);

        super.valueChanged(ctx);

        int i = 0;
        while (iter.hasNext()) {
            //Column col = (Column) iter.next();
            PropertyDescriptor pp = (PropertyDescriptor)iter.next();
            TableRow row = getRow(i++);
            TableCell cell = row.getCellAt(0);

            Label label = new Label();

            label.setValue(StringUtil.itemNameToLabel(pp.getName()));
            label.setDisplayStyle("block");
            label.setStyleClass("fieldLabel");

            FormField field = null;

            if (pp.getPropertyType().equals(String.class)) {
                field = (FormField)ClassUtil.createInstance(TextField.class);
            } else if (pp.getPropertyType().equals(Date.class)) {
                field = (FormField)ClassUtil.createInstance(DateTextField.class);
                field.setRendererType("com.jb.web.renderer.DateFieldRenderer");
                field.setDefaultValue("${session.time}");
            } else if (pp.getPropertyType().equals(Boolean.class)) {
                field = (FormField)ClassUtil.createInstance(CheckBox.class);
            } else {
                field = (FormField)ClassUtil.createInstance(TextField.class);
            }

            field.setDefault(ctx);
            field.setClientId(pp.getName());
            cell.addChild(label);
            cell.addChild(field);
        }

        TableRow row = getRow(i++);
        row.setCols(1);

        TableCell cell = row.getCellAt(0);

        Button field = (Button)ClassUtil.createInstance(Button.class);
        field.setLabel("${resource.submit}");
        field.setFormAction("submit");
        cell.addChild(field);
    }

}
